#include "Server.h"

Server::Server(int port)
{
	serverSocket = new ServerSocket(port);
	this->ROOT = "webroot/";
	this->createWebroot();
}

Server::~Server(void)
{
	delete serverSocket;
}

void Server::start() 
{
	while(true) {
		Socket *newSocket = NULL;
		try {
			newSocket = serverSocket->accept();
		
			Logger::log("Connection accepted!");

			while(newSocket->isopen()) {
				this->parseCommands(newSocket);
			}
			delete newSocket;
		} catch(int e) {
			delete newSocket;
		}
	}
}

void Server::createWebroot() {
	DIR *dir;

	dir = opendir(this->ROOT);
	if(dir == NULL) {
		mkdir(this->ROOT);
	}
	closedir(dir);
}

void Server::parseCommands(Socket *socket) {		
	char *buffer = new char[256];
	if(socket->isopen() && socket->readline(buffer, 256) > 0) {
		if(*buffer == '\0') {
			return;
		} else {			
			Logger::log("Received input: " + string(buffer));
			char *cmd;
			char *arg1 = NULL;
			char *arg2 = NULL;
			char *end;

			cmd = buffer;
			while(isspace(*cmd) && *cmd != 0) cmd++;

			end = cmd;
			while(!isspace(*end) && *end != 0) end++;

			if(*end != '\0') {
				*end = '\0';

				//Process argument 1
				arg1 = end+1;
				while(isspace(*arg1)) arg1++;

				if(*arg1 != 0) {
					end = arg1+1;
					if(*arg1 == '\'') {
						arg1 = arg1+1;
						while(*end != '\'' && *end != 0) end++;
					} else if(*arg1 == '\"') {
						arg1 = arg1+1;
						while(*end != '\"' && *end != 0) end++;
					} else {
 						while(!isspace(*end) && *end != 0) end++;
					}			

					if(*end != '\0') {
						*end = '\0';

						//Process argument 2
						arg2 = end+1;
						while(isspace(*arg2)) arg2++;
					
						if(*arg2 != 0) {
							end = arg2+1;
							if(*arg2 == '\'') {
								arg2 = arg2+1;
								while(*end != '\'' && *end != 0) end++;
							} else if(*arg2 == '\"') {
								arg2 = arg2+1;
								while(*end != '\"' && *end != 0) end++;
							} else {
 								while(!isspace(*end) && *end != 0) end++;
							}

							*end = '\0';
						}
					}
				}
			}

			processCommand(socket, cmd, arg1, arg2);
		}
	}
	delete buffer;
}

void Server::processCommand(Socket *socket, char *cmd, char *arg1, char *arg2) {
	Logger::log(string(cmd) + " command received");

	if(strcmp(cmd, "INFO") == 0) {
		char *output = "TX-Development FTP Server v0.1";
		socket->writeline(output);
	} else if(strcmp(cmd, "QUIT") == 0) {
		socket->close();
		Logger::log("Connection closed!");
	} else if(strcmp(cmd, "DIR") == 0) {
		if(arg1 == 0) {
			char *output = "Missing argument containing directory name";
			Logger::log(output);
			socket->writeline(output);
		} else {
			this->processDIRCommand(socket, arg1);
		}
	} else if(strcmp(cmd, "REN") == 0) {
		if(arg1 == 0 || arg2 == 0) {
			char *output = "Missing argument(s) containing current filename and new filename";
			Logger::log(output);
			socket->writeline(output);
		} else {
			this->processRENCommand(socket, arg1, arg2);
		}
	} else if(strcmp(cmd, "DEL") == 0) {
		if(arg1 == 0) {
			char *output = "Missing argument containing filename";
			Logger::log(output);
			socket->writeline(output);
		} else {
			this->processDELCommand(socket, arg1);
		}
	} else if(strcmp(cmd, "GET") == 0) {
		if(arg1 == 0) {
			char *output = "Missing argument containing filename";
			Logger::log(output);
			socket->writeline(output);
		} else {
			this->processGETCommand(socket, arg1);
		}
	} else if(strcmp(cmd, "PUT") == 0) {
		if(arg1 == 0 || arg2 == 0) {
			char *output = "Missing argument(s) containing filename and filecontent";
			Logger::log(output);
			socket->writeline(output);
		} else {
			this->processPUTCommand(socket, arg1, arg2);
		}
	} else {
		Logger::log("Invalid command '" + string(cmd) + "' recieved");
		socket->writeline("Invalid command given");
	}
	socket->writeline("");
}


void Server::processDIRCommand(Socket *socket, char *dirName) 
{		
	if(!this->hasAccess(socket, dirName)) {
		return;
	}

	DIR *directory;
	Logger::log("Reading directory '" + string(dirName) + "'");	

	if(strcmp(dirName, "/") == 0) {
		directory = opendir(this->ROOT);
		dirName = "";
	} else {
		directory = opendir(string(this->ROOT + string(dirName)).c_str());
	}
	if(directory == NULL) {
		Logger::log("Invalid directory '" + string(dirName) + "' recieved");	
		socket->writeline("Invalid directory");
		return;
	}	

	string dir(this->ROOT + string(dirName));	
	if(dir.substr(dir.length()-1, 1) != "/") {
		dir += "/";
	}

	struct dirent *entry;
	while((entry = readdir(directory))) {
		if(entry->d_type == DT_REG) {
			socket->write("F ");
		} else {
			socket->write("D ");
		}
		socket->write("\"");
		socket->write(entry->d_name);
		socket->write("\"");
		if(entry->d_type == DT_REG) {
			std::string path = dir + entry->d_name;
			struct stat attrib;
			stat(path.c_str(), &attrib);
			stringstream buffer;
			buffer << attrib.st_mtime;
			socket->write(" ");
			socket->write(buffer.str().c_str());
		}
		socket->writeline("");
	}
	closedir(directory);
}

void Server::processRENCommand(Socket *socket, char *currentFilename, char *newFilename)
{			
	if(!this->hasAccess(socket, currentFilename) || !this->hasAccess(socket, newFilename)) {
		return;
	}

	int result = rename(string(this->ROOT + string(currentFilename)).c_str(), string(this->ROOT + string(newFilename)).c_str());
	if (result == 0) {
		Logger::log("File '" + string(currentFilename) + "' has been renamed to '" + string(newFilename) + "'");		
		socket->writeline("File successfully renamed");
	} else {
		Logger::log("Error while renaming '" + string(currentFilename) + "'");		
		socket->writeline("Error while renaming file");
		perror("Error occured");
	}
}

void Server::processDELCommand(Socket *socket, char *filename)
{			
	if(!this->hasAccess(socket, filename)) {
		return;
	}

	string sPath = string(this->ROOT + string(filename));
	const char *path = sPath.c_str();
	int result = remove(path);
	if (result == 0) {
		Logger::log("File '" + string(filename) + "' has been deleted");	
		socket->writeline("File successfully deleted");
	} else {
		Logger::log("Error while deleting '" + string(filename) + "'");		
		socket->writeline("Error while deleting file");
	}
}

void Server::processGETCommand(Socket *socket, char *filename)
{
	if(!this->hasAccess(socket, filename)) {
		return;
	}

	std::string path = this->ROOT + string(filename);
	struct stat attrib;
	stat(path.c_str(), &attrib);
	stringstream buffer;
	buffer << attrib.st_size;
	socket->writeline(buffer.str().c_str());

	/* open file for reading in binary mode */
    ifstream infile;
    infile.open(path.c_str(), ios::in | ios::binary);
    if (infile.fail() == 1)
    {
		Logger::log("Error while opening file '" + string(filename) + "'"); 
		infile.close();
        return;
    }

    /* set cursor to the beginning of the file */
    infile.seekg(0, ios::beg);
 
    /* Since we are copying a file over the network we need to use a small buffer */
    char   buf[1024];
 
    while (!infile.eof())
    {
		infile.read (buf, sizeof(buf));
		socket->write(buf,infile.gcount());
    }
 
    infile.close();
}

void Server::processPUTCommand(Socket *socket, char *filename, char* fileArg)
{
	if(!this->hasAccess(socket, filename)) {
		return;
	}

	Logger::log("Retrieving file: " + string(filename) + ", with size: " + string(fileArg) + " bytes");

	this->createSubDirs(filename);

	unsigned long fileSize = strtoul(fileArg, NULL, 0);
	
	ofstream outputFile;
	outputFile.open(string(this->ROOT + string(filename)).c_str(), ios::out | ios::binary);
    if (outputFile.fail() == 1)
    {
        outputFile.close();
		Logger::log("Error while opening file: " + string(filename));
        return;
    }   
	outputFile.seekp(0, ios::beg);

	unsigned long readBytes = 0;
	char *buffer;
	while(fileSize - readBytes >= 1024) {
		buffer = new char[1024];
		if(socket->read(buffer, 1024) > 0) {
			outputFile.write(buffer, 1024);
		}
		delete buffer;
		readBytes += 1024;
	}
	buffer = new char[fileSize-readBytes];
	if(socket->read(buffer, fileSize-readBytes) > 0) {
		outputFile.write(buffer, fileSize-readBytes);
	}
	delete buffer;

	outputFile.close();
}

bool Server::hasAccess(Socket *socket, char *filename)
{
	if(strcmp(filename, "..") == 0 || (strncmp(filename, "..", 2) == 0 && (*(filename+2) == '\\' || *(filename+2) == '/')) || (isalpha(*filename) && *(filename+1) == ':' && (*(filename+2) == '\\' || *(filename+2) == '/'))) {
		Logger::log("Access denied on '" + string(filename) + "'");		
		socket->writeline("Access denied");
		return false;
	}
	return true;
}

void Server::createSubDirs(char *filename)
{
	string path(filename);

	string createdPath(this->ROOT);
	int start = 0;
	int end = 0;
	while(end < path.length()) {
		end = path.find("/", start);
		if(end > start) {
			createdPath = createdPath + path.substr(start, end-start) + "/";
			mkdir(createdPath.c_str());

			start = end+1;
		}
	}
}